package org.nwiz.calc;

import org.nwiz.calc.Lexer.*;

public class Eval {
    private static Token read_safe(CalcLexer lexer)
            throws Exception {
        Token tk = lexer.lex();
        if (tk == null)
            throw new Exception("Syntax error: broken expr.");
        else
            return tk;
    }

    private static boolean match(CalcLexer lexer, TokenType type)
            throws Exception {
        Token tk = read_safe(lexer);
        lexer.setLock();
        if (tk.getType() != type)
            throw new Exception("Syntax error: " + tk.toString());
        else
            return true;
    }

    private static boolean test_2(CalcLexer lexer, TokenType type_a, TokenType type_b)
            throws Exception {
        Token tk = lexer.lex();
        if (tk == null)
            return false;
        lexer.setLock();
        TokenType type = tk.getType();
        return type_a == type || type_b == type;
    }

    private static double eval_atom(CalcLexer lexer) throws Exception {
        Token tk = read_safe(lexer);
        if (tk.isNumber()) {
            return tk.getValue();
        } else if (tk.getType() == TokenType.LP) {
            double value = eval_expr(lexer);
            match(lexer, TokenType.RP);
            lexer.clearLock();
            return value;
        } else {
            throw new Exception("Cannot eval a symbol.");
        }
    }

    private static double eval_factor(CalcLexer lexer) throws Exception {
        Token tk = read_safe(lexer);
        TokenType type = tk.getType();
        if (type == TokenType.ADD) {
            return eval_factor(lexer);
        } else if (type == TokenType.SUB) {
            return -eval_factor(lexer);
        } else {
            lexer.setLock();
            return eval_atom(lexer);
        }
    }

    private static double eval_term(CalcLexer lexer) throws Exception {
        double ret = eval_factor(lexer);
        while (test_2(lexer, TokenType.MUL, TokenType.DIV)) {
            Token tk = lexer.lex();
            if (tk.getType() == TokenType.MUL)
                ret *= eval_factor(lexer);
            else
                ret /= eval_factor(lexer);
        }
        return ret;
    }


    private static double eval_expr(CalcLexer lexer) throws Exception {
        double ret = eval_term(lexer);
        while (test_2(lexer, TokenType.ADD, TokenType.SUB)) {
            Token tk = lexer.lex();
            if (tk.getType() == TokenType.ADD)
                ret += eval_term(lexer);
            else
                ret -= eval_term(lexer);
        }
        return ret;
    }

    public static double eval(String expr) throws Exception {
        return eval_expr(new CalcLexer(expr));
    }
}
